
/*
 * Copyright (C) 2008-2009 Archie L. Cobbs. All rights reserved.
 *
 * $Id: ClassPathEntityScanner.java 320 2010-02-12 18:43:29Z archie.cobbs $
 */

package org.dellroad.sidekar.spring;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.dellroad.sidekar.annotation.SidekarEntity;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;

/**
 * Scanner that scans the classpath for {@link SidekarEntity @SidekarEntity}-annotated classes,
 * starting from one or more base packages.
 *
 * @see PathMatchingResourcePatternResolver
 */
public class ClassPathEntityScanner {

    private final Logger log = Logger.getLogger(getClass());
    private final String[] basePackageNames;

    /**
     * Constructor.
     *
     * @param basePackageNames names of Java packages to scan from
     */
    public ClassPathEntityScanner(String... basePackageNames) {
        this.basePackageNames = basePackageNames;
    }

    /**
     * Scan the configured packages (and below) for annotated entity classes and return them.
     *
     * @return list of {@link SidekarEntity @SidekarEntity}-annotated classes found
     */
    public List<Class<?>> scan() {
        ArrayList<Class<?>> list = new ArrayList<Class<?>>();
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resolver);
        for (String basePackageName : this.basePackageNames) {
            String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
              + ClassUtils.convertClassNameToResourcePath(basePackageName) + "/**/*.class";
            Resource[] resources;
            try {
                resources = resolver.getResources(pattern);
            } catch (IOException e) {
                throw new RuntimeException("can't scan classpath for `" + pattern + "'", e);
            }
            for (Resource resource : resources) {
                if (!resource.isReadable())
                    continue;
                MetadataReader reader;
                try {
                    reader = readerFactory.getMetadataReader(resource);
                } catch (IOException e) {
                    log.warn("unable to read metadata from resource " + resource, e);
                    continue;
                }
                AnnotationMetadata ann = reader.getAnnotationMetadata();
                if (!ann.hasAnnotation(SidekarEntity.class.getName()))
                    continue;
                Class<?> cl;
                try {
                    cl = Class.forName(ann.getClassName(), false, Thread.currentThread().getContextClassLoader());
                } catch (ClassNotFoundException e) {
                    log.warn("unable to load class from " + resource, e);
                    continue;
                }
                list.add(cl);
            }
        }
        return list;
    }
}

